package com.winit.schedule.task.tracker.quartz;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.winit.schedule.core.conf.Configuration;
import com.winit.schedule.core.event.EventHandler;
import com.winit.schedule.core.protocol.command.QuartzRequest;
import com.winit.schedule.core.repository.constants.QuartzManagerType;
import com.winit.schedule.core.repository.entity.QuartzJobEntity;
import com.winit.schedule.core.repository.entity.QuartzLogEntity;
import com.winit.schedule.core.repository.entity.QuartzLogEntity.QuartzLogType;
import com.winit.schedule.core.repository.entity.QuartzSchedulerEntity;
import com.winit.schedule.core.repository.entity.QuartzSchedulerEntity.SchedulerStatus;
import com.winit.schedule.core.service.AbstractService;
import com.winit.schedule.core.utils.Clock;
import com.winit.schedule.task.tracker.TaskTrackerContextImpl;
import com.winit.schedule.task.tracker.conf.TaskConfiguration;
import com.winit.schedule.task.tracker.quartz.event.QuartzManagerEvent;
import com.winit.schedule.task.tracker.utils.ExceptionUtils;
import com.winit.schedule.task.tracker.utils.QuartzUtils;
import com.winit.schedule.task.tracker.utils.RemoteUtil;

public class QuartzScheduleService extends AbstractService implements EventHandler<QuartzManagerEvent> {

    private static Logger                      logger            = LoggerFactory.getLogger(QuartzScheduleService.class);

    private Map<String, TriggerStatisticsInfo> triggerStatistics = new ConcurrentHashMap<String, TriggerStatisticsInfo>();

    private Map<String, Scheduler>             schedulerMap;

    private TaskTrackerContextImpl             context;

    public QuartzScheduleService(List<Scheduler> schedulers, TaskTrackerContextImpl context) throws SchedulerException{
        super("QuartzScheduleService");
        if (null != schedulers && schedulers.size() > 0) {
            schedulerMap = new HashMap<String, Scheduler>();
            for (Scheduler scheduler : schedulers) {
                schedulerMap.put(scheduler.getSchedulerInstanceId(), scheduler);
            }

        }
        this.context = context;
    }

    public List<QuartzSchedulerEntity> getSchedulerJobs() throws SchedulerException {
        List<QuartzSchedulerEntity> schedulers = new ArrayList<QuartzSchedulerEntity>();
        List<QuartzJobEntity> jobList = null;
        QuartzSchedulerEntity schedulerEntity = null;
        if (null != schedulerMap && schedulerMap.size() > 0) {
            for (Scheduler scheduler : schedulerMap.values()) {
                schedulerEntity = new QuartzSchedulerEntity();
                schedulerEntity.setTaskGroup(this.getConfig().getString(TaskConfiguration.JT_TASK_GROUP,
                    TaskConfiguration.DEFAULT_JT_TASK_GROUP));
                schedulerEntity.setSchedulerInstanceId(scheduler.getSchedulerInstanceId());
                schedulerEntity.setSchedulerName(scheduler.getSchedulerName());
                schedulerEntity.setTaskTrackerUuid(context.getTaskTracker().getUuid());
                schedulerEntity.setSchedulerStatus(getSchdulerStatus(scheduler));
                schedulerEntity.setHeartbeatTime(Clock.getSystemTime());
                jobList = new ArrayList<QuartzJobEntity>();
                List<? extends Trigger> triggers = QuartzUtils.getAllTriggers(scheduler);
                schedulerEntity.setJobTriggerCount(triggers.size());
                for (Trigger trigger : triggers) {
                    QuartzJobEntity job = new QuartzJobEntity();
                    job.setTaskTrackerUuid(context.getTaskTracker().getUuid());
                    job.setSchedulerInstanceId(scheduler.getSchedulerInstanceId());
                    job.setJobName(trigger.getJobKey().getName());
                    job.setJobGroup(trigger.getJobKey().getGroup());
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    job.setStatus(triggerState.name());
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        job.setCronExpression(cronExpression);
                    }
                    JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey());
                    job.setDealClass(jobDetail.getJobClass().getName());
                    job.setTriggerGroup(trigger.getKey().getGroup());
                    job.setTriggerName(trigger.getKey().getName());
                    buildStatisticsInfo2Job(job);
                    jobList.add(job);
                }
                schedulerEntity.setJobs(jobList);
                schedulers.add(schedulerEntity);
            }
        }

        return schedulers;
    }

    private void buildStatisticsInfo2Job(QuartzJobEntity job) {
        String key = job.getSchedulerInstanceId() + "." + job.getTriggerGroup() + "." + job.getTriggerName();
        TriggerStatisticsInfo hisStatisticsInfo = triggerStatistics.get(key);
        if (null != hisStatisticsInfo) {
            job.setMinRuntime(hisStatisticsInfo.getMinJobRuntime());
            job.setMaxRuntime(hisStatisticsInfo.getMaxJobRuntime());
            job.setAvgRuntime(hisStatisticsInfo.getAvgJobRuntime());
            job.setRunCount(hisStatisticsInfo.getRunCount());
            job.setLastTriggerTime(hisStatisticsInfo.getLastTriggerTime());
        }
    }

    private SchedulerStatus getSchdulerStatus(Scheduler scheduler) throws SchedulerException {
        return scheduler.isShutdown() ? SchedulerStatus.SHUTDOWN : (scheduler.isInStandbyMode() ? SchedulerStatus.STANDBY : (scheduler.isStarted() ? SchedulerStatus.STARTED : SchedulerStatus.UNKONW));
    }

    public void pauseAll(QuartzSchedulerEntity shchedulerEntity) throws SchedulerException {
        if (null == shchedulerEntity && null != schedulerMap) {
            for (Scheduler scheduler : getSchedulers()) {
                scheduler.pauseAll();
            }
        } else {
            Scheduler scheduler = schedulerMap.get(shchedulerEntity.getSchedulerInstanceId());
            if (null != scheduler) {
                scheduler.pauseAll();
            }
        }
    }

    public void resumeAll(QuartzSchedulerEntity shchedulerEntity) throws SchedulerException {
        if (null == shchedulerEntity && null != schedulerMap) {
            for (Scheduler scheduler : getSchedulers()) {
                scheduler.resumeAll();
            }
        } else {
            Scheduler scheduler = schedulerMap.get(shchedulerEntity.getSchedulerInstanceId());
            if (null != scheduler) {
                scheduler.resumeAll();
            }
        }
    }

    /**
     * <p>
     * 暂停一个JOB
     * </p>
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void pauseTrigger(QuartzJobEntity scheduleJob) throws SchedulerException {
        QuartzUtils.pauseTrigger(schedulerMap.get(scheduleJob.getSchedulerInstanceId()),
            scheduleJob.getTriggerName(),
            scheduleJob.getTriggerGroup());
    }

    /**
     * <p>
     * 恢复一个JOB
     * </p>
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void resumeTrigger(QuartzJobEntity scheduleJob) throws SchedulerException {
        QuartzUtils.resumeTrigger(schedulerMap.get(scheduleJob.getSchedulerInstanceId()),
            scheduleJob.getTriggerName(),
            scheduleJob.getTriggerGroup());
    }

    /**
     * <p>
     * 更新JOB时间表达式
     * </p>
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void rescheduleJob(QuartzJobEntity scheduleJob) throws SchedulerException {
        QuartzUtils.rescheduleJob(schedulerMap.get(scheduleJob.getSchedulerInstanceId()),
            scheduleJob.getTriggerName(),
            scheduleJob.getTriggerGroup(),
            scheduleJob.getCronExpression());
    }

    public Map<String, Scheduler> getSchedulerMap() {
        return schedulerMap;
    }

    public Collection<Scheduler> getSchedulers() {
        if (null != schedulerMap && schedulerMap.size() > 0) {
            return schedulerMap.values();
        }
        return null;
    }

    @Override
    protected void serviceInit(Configuration conf) throws Exception {
        super.serviceInit(conf);
    }

    @Override
    protected void serviceStart() throws Exception {
        super.serviceStart();
    }

    @Override
    protected void serviceStop() throws Exception {
        super.serviceStop();
    }

    @Override
    public void handle(QuartzManagerEvent event) {
        QuartzManagerType type = event.getType();
        if (null != type) {

            if (logger.isDebugEnabled()) {
                logger.debug("Quartz execute the command: " + type);
            }

            if (QuartzManagerType.UPDATE_STAT.equals(type)) {
                TriggerStatisticsInfo statisticsInfo = event.getStatisticsInfo();
                TriggerStatisticsInfo hisStatisticsInfo = triggerStatistics.get(statisticsInfo.getKey());
                if (null != hisStatisticsInfo) {
                    hisStatisticsInfo.addJobRuntime(statisticsInfo.getAvgJobRuntime());
                    hisStatisticsInfo.setLastTriggerTime(statisticsInfo.getLastTriggerTime());
                } else {
                    triggerStatistics.put(statisticsInfo.getKey(), statisticsInfo);
                }
            } else if (QuartzManagerType.SCHEDULER_EXCEPTION.equals(type)) {
                QuartzRequest request = new QuartzRequest();
                request.setLogEntity(event.getLogEntity());
                RemoteUtil.sendQuartzCommand(request, this.context.getRemotingClientService());
            } else {
                handleRemoteCommand(event);
            }

        }
    }

    private void handleRemoteCommand(QuartzManagerEvent event) {
        QuartzManagerType type = event.getType();
        QuartzLogEntity logEntity = new QuartzLogEntity();
        logEntity.setTaskGroup(this.getConfig().getString(TaskConfiguration.JT_TASK_GROUP,
            TaskConfiguration.DEFAULT_JT_TASK_GROUP));
        boolean isTriggerCmd = false;
        try {
            if (QuartzManagerType.PASUE_TRIGGER.equals(type)) {
                this.pauseTrigger(event.getJobEntity());
                isTriggerCmd = true;
            } else if (QuartzManagerType.RESUME_TRIGGER.equals(type)) {
                this.resumeTrigger(event.getJobEntity());
                isTriggerCmd = true;
            } else if (QuartzManagerType.RESCHEDULE.equals(type)) {
                this.rescheduleJob(event.getJobEntity());
                isTriggerCmd = true;
            } else if (QuartzManagerType.PASUE_ALL.equals(type)) {
                this.pauseAll(event.getSchedulerEntity());
                isTriggerCmd = false;
            } else if (QuartzManagerType.RESUME_ALL.equals(type)) {
                this.resumeAll(event.getSchedulerEntity());
                isTriggerCmd = false;
            }

            if (isTriggerCmd) {
                logEntity.setTaskTrackerUuid(event.getJobEntity().getTaskTrackerUuid());
                logEntity.setSchedulerInstanceId(event.getJobEntity().getSchedulerInstanceId());
            } else {
                logEntity.setTaskTrackerUuid(event.getSchedulerEntity().getTaskTrackerUuid());
                logEntity.setSchedulerInstanceId(event.getSchedulerEntity().getSchedulerInstanceId());
            }

            logEntity.setLogType(QuartzLogType.COMMAND_EXE_SUCCESS);
            logEntity.setMessage(String.format("[%s] command execution success", type));
        } catch (SchedulerException e) {
            logEntity.setLogType(QuartzLogType.COMMAND_EXE_ERROR);
            logEntity.setMessage(String.format("[%s] command execution failed", type));
            logEntity.setCause(ExceptionUtils.getStackTrace(e));
            logger.error(String.format("command execution failed : %s", type), e);

        }
        logEntity.setLogTime(Clock.getSystemTime());
        QuartzRequest request = new QuartzRequest();
        request.setLogEntity(logEntity);
        RemoteUtil.sendQuartzCommand(request, this.context.getRemotingClientService());
    }
}
